#include <GL/glfw.h>

#include "Window.h"
#include "WindowListener.h"

#include "Mouse.h"
#include "Keyboard.h"
#include "Renderer.h"

using namespace std;

namespace core
{

void GLFWCALL ResizeCallback( int width, int height );
int GLFWCALL CloseCallback( );
void GLFWCALL RefreshCallback( );
void GLFWCALL KeyCallback( int key, int state );
void GLFWCALL MouseButtonCallback( int button, int state );
void GLFWCALL MousePosCallback( int x, int y );
void GLFWCALL MouseWheelCallback( int pos );

Vec2 ConvertMousePos( int x, int y );
MouseButton ConvertMouseButton( int button );
KeyboardKey ConvertKeyboardKey( int key );

Vector<IWindowListener*> Window::_listeners;

const char* Window::_title( "CandoEngine" );
Point Window::_position( 0, 0 );
Size Window::_size( 800, 600 );
Window::Mode Window::_mode = Window::Windowed;

Point Window::GetPosition( )
{
	return _position;
}

void Window::SetPosition( const Point& pos )
{
	_position = pos;
	glfwSetWindowPos( _position.X, _position.Y );
}

Size Window::GetSize( )
{
	int width;
	int height;
	glfwGetWindowSize( &width, &height );
	return Size( width, height );
}

void Window::SetSize( const Size& size )
{
	_size = size;
	glfwSetWindowSize( _size.Width, _size.Height );
}

const char* Window::GetTitle( )
{
	return _title;
}

void Window::SetTitle( const char* title )
{
	_title = title;
	glfwSetWindowTitle( _title );
}

Window::Mode Window::GetMode( )
{
	return _mode;
}

void Window::SetMode( const Window::Mode& mode )
{
	_mode = mode;
	Close( );
	Open( );
}

bool Window::IsOpened( )
{
	return glfwGetWindowParam( GLFW_OPENED ) == GL_TRUE;
}

bool Window::IsActive( )
{
	return glfwGetWindowParam( GLFW_ACTIVE ) == GL_TRUE;
}

bool Window::IsIconified( )
{
	return glfwGetWindowParam( GLFW_ICONIFIED ) == GL_TRUE;
}

int WindowModeToInt( Window::Mode mode )
{
	switch( mode )
	{
	case Window::Windowed:
		return GLFW_WINDOW;
	case Window::Fullscreen:
		return GLFW_FULLSCREEN;
	}
	throw 0;
}

void Window::Create( const Point& position, const Size& size, const char* title, const Mode& mode, const Color& backgroundColor )
{
	_position = position;
	_size = size;
	_title = title;
	_mode = mode;

	Renderer::Create( backgroundColor );
}

void Window::Open( )
{
	glfwOpenWindowHint( GLFW_FSAA_SAMPLES, 4 );
	glfwOpenWindow( _size.Width, _size.Height, 8, 8, 8, 8, 8, 8, WindowModeToInt( _mode ) );
	glfwSwapInterval( 0 );

	SetPosition( _position );
	SetTitle( _title );

	glfwDisable( GLFW_AUTO_POLL_EVENTS );

	glfwSetWindowSizeCallback( ResizeCallback );
	glfwSetWindowCloseCallback( CloseCallback );
	glfwSetWindowRefreshCallback( RefreshCallback );

	glfwSetKeyCallback( KeyCallback );
	glfwSetMouseButtonCallback( MouseButtonCallback );
	glfwSetMousePosCallback( MousePosCallback );
	glfwSetMouseWheelCallback( MouseWheelCallback );
}

void Window::Update( )
{
	glfwPollEvents( );
}

void Window::Close( )
{
	glfwCloseWindow( );
}

void Window::Destroy( )
{
	Renderer::Destroy( );
	exit( EXIT_SUCCESS );
}

void Window::Iconify( )
{
	glfwIconifyWindow( );
}

void Window::Restore( )
{
	glfwRestoreWindow( );
}

void Window::AddListener( IWindowListener* listener )
{
	_listeners.Add( listener );
}

void Window::RemoveListener( IWindowListener* listener )
{
	_listeners.Remove( listener );
}

void Window::Resized( const Size& size )
{
	_size = size;

	for( IteratorPtr<IWindowListener*> listeners( _listeners.GetIterator( ) );
			!listeners->IsEnd( );
			listeners->Next( ) )
	{
		listeners->Current( )->Resize( size );
	}
}

void Window::Closed( )
{
	for( IteratorPtr<IWindowListener*> listeners( _listeners.GetIterator( ) );
			!listeners->IsEnd( );
			listeners->Next( ) )
	{
		listeners->Current( )->Close( );
	}
}

void Window::Refreshed( )
{
	for( IteratorPtr<IWindowListener*> listeners( _listeners.GetIterator( ) );
			!listeners->IsEnd( );
			listeners->Next( ) )
	{
		listeners->Current( )->Refresh( );
	}
}

void GLFWCALL ResizeCallback( int width, int height )
{
	Size size = Size( width, height );
	Window::Resized( size );
	Renderer::WindowResized( size );
}

int GLFWCALL CloseCallback( )
{
	Window::Closed( );
	return GL_TRUE;
}

void GLFWCALL RefreshCallback( )
{
	Window::Refreshed( );
}

void GLFWCALL KeyCallback( int key, int state )
{
	if( state == GLFW_PRESS )
		Keyboard::OnPressKey( ConvertKeyboardKey( key ) );
	if( state == GLFW_RELEASE )
		Keyboard::OnReleaseKey( ConvertKeyboardKey( key ) );
}

void GLFWCALL MouseButtonCallback( int button, int state )
{
	if( state == GLFW_PRESS )
		Mouse::OnPressButton( ConvertMouseButton( button ) );
	if( state == GLFW_RELEASE )
		Mouse::OnReleaseButton( ConvertMouseButton( button ) );
}

void GLFWCALL MousePosCallback( int x, int y )
{
	Mouse::OnMoveCursor( ConvertMousePos( x, y ) );
}

void GLFWCALL MouseWheelCallback( int pos )
{
	Mouse::OnMoveWheel( pos );
}

Vec2 ConvertMousePos( int x, int y )
{
	static float matr[16];
	glGetFloatv( GL_VIEWPORT, matr );
	float width = matr[2];
	float height = matr[3];
	glGetFloatv( GL_MODELVIEW_MATRIX, matr );

	float scale = matr[10];
	Vec2 cam(-matr[12], -matr[13]);
	float worldX = ( ( x * 2.0f / width - 1.0f ) * width / height + cam.X ) / scale;
	float worldY = ( -( y * 2.0f / height - 1.0f ) + cam.Y ) / scale;

	return Vec2( worldX, worldY );
}

MouseButton ConvertMouseButton( int button )
{
	return MouseButton( button );
}

KeyboardKey ConvertKeyboardKey( int key )
{
	return KeyboardKey( key );
}
}
